package com.gaofans.vertx.boot.web.exception;

import com.gaofans.vertx.boot.web.annotation.ExceptionAdvice;
import com.gaofans.vertx.boot.web.annotation.ExceptionHandler;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 用注解查找exceptionHandler
 * @author GaoFans
 */
public class AnnotationExceptionHandlerDiscoverer implements ApplicationContextAware,ExceptionHandlerDiscoverer{

    private ApplicationContext applicationContext;
    private Map<String, List<ExceptionHandlerDefinition>> resultMap;
    private static final Logger LOG = LoggerFactory.getLogger(AnnotationExceptionHandlerDiscoverer.class);
    @Override
    @SuppressWarnings("all")
    public synchronized Map<String, List<ExceptionHandlerDefinition>> findExceptionHandler() {
        if(resultMap != null){
            return resultMap;
        }
        String[] beanNames = this.applicationContext.getBeanNamesForAnnotation(ExceptionAdvice.class);
        Map<String, List<ExceptionHandlerDefinition>> map = new HashMap<String, List<ExceptionHandlerDefinition>>(beanNames.length);
        for (String beanName : beanNames) {
            Object bean = this.applicationContext.getBean(beanName);
            ExceptionAdvice advice = AnnotationUtils.findAnnotation(bean.getClass(), ExceptionAdvice.class);
            for (String pattern : advice.patterns()) {
                map.computeIfAbsent(pattern,k -> new ArrayList<>());
            }
            for (Method method : bean.getClass().getMethods()) {
                ExceptionHandler annotation = AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
                if(annotation != null){
                    ExceptionHandlerDefinition definition = new ExceptionHandlerDefinition();
                    definition.setCauses(annotation.value());
                    definition.setCode(annotation.status());
                    definition.setHandler((Handler<RoutingContext>) ReflectionUtils.invokeMethod(method,bean));
                    LOG.debug("找到ExceptionHandler: patterns[{}],exceptions[{}]",
                            Arrays.stream(advice.patterns()).collect(Collectors.joining(",")),
                            Arrays.stream(annotation.value()).map(aClass -> aClass.toString()).collect(Collectors.joining(",")));
                    for (String pattern : advice.patterns()) {
                        map.get(pattern).add(definition);
                    }
                }
            }
        }
        resultMap = Collections.unmodifiableMap(map);
        return map;
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
